/*
 *    Copyright (C) 2012 Hai Bison
 *
 *    See the file LICENSE at the root directory of this project for copying
 *    permission.
 */

package group.pals.desktop.app.apksigner;

import group.pals.desktop.app.apksigner.i18n.Messages;
import group.pals.desktop.app.apksigner.i18n.R;
import group.pals.desktop.app.apksigner.services.INotification;
import group.pals.desktop.app.apksigner.services.ServiceManager;
import group.pals.desktop.app.apksigner.utils.Files;
import group.pals.desktop.app.apksigner.utils.Preferences;
import group.pals.desktop.app.apksigner.utils.Texts;
import group.pals.desktop.app.apksigner.utils.ZipAlign;
import group.pals.desktop.app.apksigner.utils.ZipAlign.ZipAligner;
import group.pals.desktop.app.apksigner.utils.ZipAlign.ZipAlignmentVerifier;
import group.pals.desktop.app.apksigner.utils.ui.Dlg;
import group.pals.desktop.app.apksigner.utils.ui.FileDrop;
import group.pals.desktop.app.apksigner.utils.ui.JEditorPopupMenu;
import group.pals.desktop.app.apksigner.utils.ui.UI;

import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.Beans;
import java.io.File;
import java.util.regex.Matcher;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

/**
 * APK alignment tools.
 * 
 * @author Hai Bison
 * @since v1.6.9 beta
 */
public class PanelApkAlignment extends JPanel {

    /**
     * Auto-generated by Eclipse.
     */
    private static final long serialVersionUID = -8324003229922201602L;

    /**
     * The class name.
     */
    private static final String CLASSNAME = PanelApkAlignment.class.getName();

    /**
     * This key holds the last working directory.
     */
    private static final String PKEY_LAST_WORKING_DIR = CLASSNAME
            + ".last_working_dir";

    /*
     * FIELDS
     */

    private File mApkFile;

    /*
     * CONTROLS
     */

    private JButton mBtnLoadApkFile;
    private JButton mBtnVerify;
    private JButton mBtnAlign;
    private JTextArea mTextInfo;
    private JProgressBar mProgressBar;
    private JScrollPane mTextInfoScrollPane;

    /**
     * Create the panel.
     */
    public PanelApkAlignment() {
        setLayout(new BorderLayout(10, 10));

        JPanel panel = new JPanel();
        add(panel, BorderLayout.NORTH);

        JPanel panel_1 = new JPanel();
        panel.setLayout(new BorderLayout(5, 5));
        panel.add(panel_1, BorderLayout.CENTER);

        mBtnLoadApkFile = new JButton(
                Messages.getString(R.string.desc_load_apk_file));
        mBtnLoadApkFile.addActionListener(mBtnLoadApkFileActionListener);
        new FileDrop(mBtnLoadApkFile, BorderFactory.createCompoundBorder(
                UI.BORDER_FILE_DROP, mBtnLoadApkFile.getBorder()),
                mBtnLoadApkFileFileDropListener);
        panel_1.add(mBtnLoadApkFile);

        mBtnVerify = new JButton(Messages.getString(R.string.verify));
        mBtnVerify.addActionListener(mBtnVerifyActionListener);
        panel_1.add(mBtnVerify);

        mBtnAlign = new JButton(Messages.getString(R.string.align));
        mBtnAlign.addActionListener(mBtnAlignActionListener);
        panel_1.add(mBtnAlign);

        JSeparator separator = new JSeparator();
        panel.add(separator, BorderLayout.NORTH);

        mProgressBar = new JProgressBar();
        panel.add(mProgressBar, BorderLayout.SOUTH);

        mTextInfoScrollPane = new JScrollPane();
        add(mTextInfoScrollPane, BorderLayout.CENTER);

        mTextInfo = new JTextArea();
        mTextInfo.setTabSize(UI.TEXT_COMPONENT_TAB_SIZE);
        mTextInfo.setMargin(new Insets(9, 9, 9, 9));
        if (!Beans.isDesignTime())
            mTextInfo.setFont(Assets.getDefaultMonoFont());
        mTextInfo.setEditable(false);
        mTextInfoScrollPane.setViewportView(mTextInfo);

        JEditorPopupMenu.apply(this);
    }// PanelApkAlignment()

    /**
     * Enables/ disables commands.
     * 
     * @param enabled
     *            {@code true} or {@code false}.
     */
    private void enableCommands(boolean enabled) {
        for (JComponent comp : new JComponent[] { mBtnLoadApkFile, mBtnVerify,
                mBtnAlign })
            comp.setEnabled(enabled);
    }// enableCommands()

    /**
     * Resets output fields.
     */
    private void resetOutputFields() {
        mProgressBar.setValue(0);
        mTextInfo.setText(null);
    }// resetOutputFields()

    /**
     * Validates all fields.
     * 
     * @return {@code true} or {@code false}.
     */
    private boolean validateFields() {
        if (mApkFile == null || !mApkFile.isFile()) {
            Dlg.showInfoMsg(Messages.getString(R.string.msg_load_apk_file));
            return false;
        }

        return true;
    }// validateFields()

    /**
     * Sets the APK file.
     * 
     * @param file
     *            the APK file, can be {@code null}.
     */
    private void setApkFile(File file) {
        if (file != null && file.isFile()) {
            mApkFile = file;

            mBtnLoadApkFile.setText(mApkFile.getName());
            mBtnLoadApkFile.setForeground(UI.COLOUR_SELECTED_FILE);
            Preferences.getInstance().set(PKEY_LAST_WORKING_DIR,
                    mApkFile.getParentFile().getAbsolutePath());
        } else {
            mApkFile = null;

            mBtnLoadApkFile.setText(Messages
                    .getString(R.string.desc_load_apk_file));
            mBtnLoadApkFile.setForeground(UI.COLOUR_WAITING_CMD);
        }

        resetOutputFields();
    }// setApkFile()

    /*
     * LISTENERS
     */

    private final FileDrop.Listener mBtnLoadApkFileFileDropListener = new FileDrop.Listener() {

        @Override
        public void onFilesDropped(File[] files) {
            setApkFile(files[0]);
        }// onFilesDropped()
    };// mBtnLoadApkFileFileDropListener

    private final ActionListener mBtnLoadApkFileActionListener = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            setApkFile(Files.chooseFile(
                    new File(Preferences.getInstance().get(
                            PKEY_LAST_WORKING_DIR, "/")),
                    Texts.REGEX_APK_FILES,
                    Messages.getString(R.string.desc_apk_files)));
        }// actionPerformed()
    };// mBtnLoadApkFileActionListener

    private final ActionListener mBtnVerifyActionListener = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!validateFields())
                return;

            resetOutputFields();
            enableCommands(false);

            final ZipAlignmentVerifier verifier = new ZipAlignmentVerifier(
                    mApkFile);
            ServiceManager.registerThread(verifier);
            verifier.addNotification(new INotification() {

                private long lastUpdate = 0;

                @Override
                public boolean onMessage(final Message msg) {
                    switch (msg.id) {
                    case ZipAlignmentVerifier.MSG_ERROR:
                        mTextInfo.append("\n\n");
                        mTextInfo.append(msg.detailedMessage);
                        break;
                    case ZipAlignmentVerifier.MSG_DONE:
                        enableCommands(true);
                        break;
                    default:
                        if (!Texts.isEmpty(msg.detailedMessage))
                            mTextInfo.append(msg.detailedMessage);
                        break;
                    }

                    if (msg.id == ZipAlignmentVerifier.MSG_INFO
                            && msg.obj instanceof Double) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                mProgressBar.setValue((int) Math
                                        .round((Double) msg.obj));
                            }// run()
                        });
                    }// if

                    if (System.currentTimeMillis() - lastUpdate >= UI.DELAY_TIME_UPDATING_UI
                            || msg.id == ZipAlignmentVerifier.MSG_DONE) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                mTextInfoScrollPane.getHorizontalScrollBar()
                                        .setValue(0);
                                mTextInfoScrollPane.getVerticalScrollBar()
                                        .setValue(Integer.MAX_VALUE);
                            }// run()
                        });

                        lastUpdate = System.currentTimeMillis();
                    }// if

                    return false;
                }// onMessage()
            });

            verifier.start();
        }// actionPerformed()
    };// mBtnVerifyActionListener

    private final ActionListener mBtnAlignActionListener = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!validateFields())
                return;

            if (!mApkFile.getName().matches(Texts.REGEX_APK_FILES)) {
                if (!Dlg.confirmYesNo(Messages.getString(
                        R.string.pmsg_confirm_aligning_file_not_apk,
                        mApkFile.getName()), false))
                    return;
            }

            /*
             * Build output file name.
             */
            final String srcName = mApkFile.getName();
            String newName;
            if (srcName.matches("(?si).*?unaligned.+"))
                newName = srcName.replaceFirst("(?si)unaligned",
                        Matcher.quoteReplacement(ZipAlign.ALIGNED));
            else
                newName = Files.appendFilename(srcName, "_" + ZipAlign.ALIGNED);

            final File outputFile = new File(mApkFile.getParent()
                    + File.separator + newName);
            if (outputFile.isFile()) {
                if (!Dlg.confirmYesNo(Messages.getString(
                        R.string.pmsg_override_file, outputFile.getName()),
                        false))
                    return;
            }

            resetOutputFields();
            enableCommands(false);

            final ZipAligner zipAligner = new ZipAligner(mApkFile, outputFile);
            ServiceManager.registerThread(zipAligner);
            zipAligner.addNotification(new INotification() {

                private long lastUpdate = 0;

                @Override
                public boolean onMessage(final Message msg) {
                    switch (msg.id) {
                    case ZipAligner.MSG_ERROR:
                        mTextInfo.append("\n\n");
                        mTextInfo.append(msg.detailedMessage);
                        break;
                    case ZipAligner.MSG_DONE:
                        enableCommands(true);
                        break;
                    default:
                        if (!Texts.isEmpty(msg.detailedMessage))
                            mTextInfo.append(msg.detailedMessage);
                        break;
                    }

                    if (msg.id == ZipAligner.MSG_INFO
                            && msg.obj instanceof Double) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                mProgressBar.setValue((int) Math
                                        .round((Double) msg.obj));
                            }// run()
                        });
                    }// if

                    if (System.currentTimeMillis() - lastUpdate >= UI.DELAY_TIME_UPDATING_UI
                            || msg.id == ZipAligner.MSG_DONE) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                mTextInfoScrollPane.getHorizontalScrollBar()
                                        .setValue(0);
                                mTextInfoScrollPane.getVerticalScrollBar()
                                        .setValue(Integer.MAX_VALUE);
                            }// run()
                        });

                        lastUpdate = System.currentTimeMillis();
                    }// if

                    return false;
                }// onMessage()
            });

            zipAligner.start();
        }// actionPerformed()
    };// mBtnAlignActionListener

}
